JavaScript 中的对象属性,并不只是简单的键值对,通过属性描述符 property descriptor ,我们可以更加灵活的配置对象属性以控制属性的行为,包括是否可枚举、是否可写、是否可配置,实现更强大的功能。实际上,对于对象而言,一共有 2 种属性:
- 数据属性 data property
- 访问属性 accessor property
数据属性
通常我们见到的属性都是这样的:
1 | // 代码片段 1 |
上面的 name 和 age 都是数据属性。
访问属性
还有另一种形式,比如:
1 | // 代码片段 2 |
上面的 name 和 surname 是数据属性,fullname 则是访问属性。即凡是使用 get prop() 或者 set prop() 定义的属性,都是访问属性,不再是数据属性。
数据属性的描述符
对于数据属性而言,其描述符由 4 个 flag 组成:
value属性的值writable是否可写enumerable是否可枚举,在类似for...in遍历中是否忽略configurable是否可配置,即是否可以编辑描述符的flag
对于使用字面量声明的对象属性而言,除了 value 之外的 3 个 flag 的默认值都为 true。使用 Object.getOwnPropertyDescriptor(obj, prop) 方法可以获取对象上某属性的描述符。比如获取上面代码片段 1 中 user 对象 name 属性的描述符:
1 | // 代码片段 3 |
如果想要精确地控制属性的行为,使用 Object.defineProperty(obj, prop, descriptor) 方法。使用这种方式,如果属性的某个 flag 没有显式指定,则默认值将是 false 。
1 | // 代码片段 4 |
可以看到,除了显式指定的 writable ,其他未指定的 flag 的都是默认为 false。
可枚举性
描述符的 enumerable 属性,称为“可枚举性”,如果该属性为 false,就表示某些操作会忽略当前属性。
目前,有四个操作会忽略 enumerable 为 false 的属性。
for...in循环:只遍历对象自身的和继承的可枚举的属性。Object.keys():返回对象自身的所有可枚举的属性的键名。JSON.stringify():只串行化对象自身的可枚举的属性。Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
可配置性
描述符的 configurable 属性,表示该属性的 flag 是否可配置,一旦设为 false 不可逆。用的很少。
访问属性的描述符
访问属性的描述符不同于数据属性的描述符,没有 value 和 writable 这 2 个 flag,多了 get 和 set,所以一个访问属性的描述符可能有如下组成:
get– 无参数函数,当属性被读取时被调用set– 单参数函数,当属性被设置时被调用enumerable– 与数据属性一致configurable– 与数据属性一致
访问属性的 getter/setter 可以直接使用字面量声明(如上面的代码片段 2 所示),也可以使用 Object.defineProperty,如下:
1 | // 代码片段 5 |
getter/setter 应用
使用 get 和 set 访问属性描述符,我们可以更加灵活控制属性的读写行为。比如对于 user 对象而言,可以限制 name 的长度:
1 | // 代码片段 6 |
数据属性或访问属性
需要注意的是,一个属性要么是数据属性,要么是访问属性,不能两者皆是。相应地,如果同时给属性设置 value 和 get 这 2 个 flag 会报错。
1 | // 代码片段 7 |